#pragma once
#include "Runtime/BattleFirePrefix.h"
#include "Event.h"
#include "WorkerThread.h"
#include "RunnableTask.h"
#include "PooledThreadBody.h"

namespace BattleFire {
	struct CompositedThread
	{
		PooledThreadBody*	mThreadBody;
		WorkerThread*	mWorkerThread;
		bool			mIsAttached;
		CompositedThread()
			: mThreadBody(nullptr)
			, mWorkerThread(nullptr)
			, mIsAttached(false)
		{
		}
	};
	class ThreadPoolInterface
	{
	public:
		static ThreadPoolInterface& Get();
		static void Startup(int32 inThreadsCount);
		static void Shutdown();
		static bool IsRunning();
	public:
		virtual void QueueTask(RunnableTask* Task, ENamedThreads ThreadToExecuteOn, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) = 0;
	public:
		virtual ~ThreadPoolInterface()
		{
		}
		virtual ENamedThreads GetCurrentThreadIfKnown(bool bLocalQueue = false) = 0;
		virtual	int32 GetNumWorkerThreads() = 0;
		virtual bool IsThreadProcessingTasks(ENamedThreads ThreadToCheck) = 0;
		virtual void AttachToThread(ENamedThreads CurrentThread) = 0;
		virtual uint64 ProcessThreadUntilIdle(ENamedThreads CurrentThread) = 0;
		virtual void ProcessThreadUntilRequestReturn(ENamedThreads CurrentThread) = 0;
		virtual void RequestReturn(ENamedThreads CurrentThread)=0;
		virtual void WaitUntilTasksComplete(const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)=0;
		virtual void WaitUntilTasksComplete(ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) = 0;
		virtual void TriggerEventWhenTasksComplete(BattleFireEvent* InEvent, const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread, ENamedThreads TriggerThread = ENamedThreads::AnyHiPriThreadHiPriTask) = 0;
		virtual void AddShutdownCallback(void(*Callback)()) = 0;
		virtual void WakeNamedThread(ENamedThreads ThreadToWake) = 0;
		void WaitUntilTaskCompletes(const RunnableTaskEventRef& Task, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			WaitUntilTasksComplete({ Task }, CurrentThreadIfKnown);
		}
		void WaitUntilTaskCompletes(RunnableTaskEventRef&& Task, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			WaitUntilTasksComplete({ std::forward<RunnableTaskEventRef>(Task) }, CurrentThreadIfKnown);
		}
		void TriggerEventWhenTaskCompletes(BattleFireEvent* InEvent, const RunnableTaskEventRef& Task, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread, ENamedThreads TriggerThread = ENamedThreads::AnyHiPriThreadHiPriTask)
		{
			RunnableTaskEventArray Prerequistes;
			Prerequistes.push_back(Task);
			TriggerEventWhenTasksComplete(InEvent, Prerequistes, CurrentThreadIfKnown, TriggerThread);
		}
		virtual RunnableTask* FindWork(ENamedThreads ThreadInNeed){return nullptr;}
	};
	class ThreadPool : public ThreadPoolInterface
	{
	friend class RunnableTask;
	public:
	ThreadPool(int32 threadCount);
	protected:
		virtual void QueueTask(RunnableTask* Task, ENamedThreads ThreadToExecuteOn, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) override;
	public:
		virtual ENamedThreads GetCurrentThreadIfKnown(bool bLocalQueue = false) override;
		virtual	int32 GetNumWorkerThreads() override;
		virtual bool IsThreadProcessingTasks(ENamedThreads ThreadToCheck) override;
		virtual void AttachToThread(ENamedThreads CurrentThread) override;
		virtual uint64 ProcessThreadUntilIdle(ENamedThreads CurrentThread) override;
		virtual void ProcessThreadUntilRequestReturn(ENamedThreads CurrentThread) override;
		virtual void RequestReturn(ENamedThreads CurrentThread) override;
		virtual void WaitUntilTasksComplete(const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) override;
		virtual void WaitUntilTasksComplete(ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) override;
		virtual void TriggerEventWhenTasksComplete(BattleFireEvent* InEvent, const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread, ENamedThreads TriggerThread = ENamedThreads::AnyHiPriThreadHiPriTask) override;
		virtual void AddShutdownCallback(void(*Callback)()) override;
		virtual void WakeNamedThread(ENamedThreads ThreadToWake) override;
	public:
		PooledThreadBody& ThreadBody(int32 Index)
		{
			return *mCompositedThreads[Index].mThreadBody;
		}
		ENamedThreads GetCurrentThread();
		BattleFireSInt32 ThreadIndexToPrioritySetIndex(BattleFireSInt32 ThreadIndex);
		void StartTaskThread(int32 Priority, int32 IndexToStart);
		void StartAllTaskThreads(bool bDoBackgroundThreads);
		RunnableTask* FindWork(ENamedThreads ThreadInNeed);
		void StallForTuning(int32 Index, bool Stall);
		
		#define CREATE_HIPRI_TASK_THREADS (1)
		#define CREATE_BACKGROUND_TASK_THREADS (1)
		enum
		{
			/** Compile time maximum number of threads. Didn't really need to be a compile time constant, but task thread are limited by MAX_LOCK_FREE_LINKS_AS_BITS **/
			MAX_THREADS = 26 * (CREATE_HIPRI_TASK_THREADS + CREATE_BACKGROUND_TASK_THREADS + 1) + (int32)ENamedThreads::ActualRenderingThread + 1,
			MAX_THREAD_PRIORITIES = 3
		};
		/** Per thread data. **/
		CompositedThread	mCompositedThreads[MAX_THREADS];
		BattleFireSInt32	mThreadsCount;
		int32				mNamedThreadCount;
		int32				mTaskThreadSetCount;
		BattleFireSInt32	mTaskThreadCountPerSet;
		bool				mbCreatedHiPriorityThreads;
		bool				mbCreatedBackgroundPriorityThreads;
		ENamedThreads		mLastNamedThread;
		uint32				mPerThreadIDTLSSlot;

		std::list<void(*)()> ShutdownCallbacks;

		StallingTaskQueue<RunnableTask, PLATFORM_CACHE_LINE_SIZE, 2>	mTasksForNonNamedThreads[MAX_THREAD_PRIORITIES];
	};
}